feat(postgres): PROXY protocol support + per-listener proxy_protocol config#55
Conversation
…l config Adds PROXY protocol v1/v2 support to the Postgres data-plane listener, matching the HTTP/CONNECT listener's existing fail-open handling: a leading PROXY header is honored when present (client_ip and the run-token-auth-failure log line reflect its advertised address instead of the raw TCP peer), a connection with no header still succeeds (fail-open), and a malformed header is dropped with one DEBUG log line. The header always arrives as the first bytes on the wire, ahead of the client's SSLRequest, so PostgresServer gains a StartListener(net.Listener) method letting the caller wrap the listener before the first Accept. Both listeners now share one implementation, proxy.WrapProxyProtocolListener (proxy/proxyproto.go) — the proxyproto.Listener construction, the fail-open USE policy, the 10s ReadHeaderTimeout, and the malformed-header debug-log wrapper, previously duplicated inline in gatekeeper.go for the HTTP listener only. Configuration is reorganized to be per-listener: network.proxy_protocol is removed (gatekeeper is pre-1.0 and single-consumer, so this is a breaking config change with no deprecated alias) in favor of proxy.proxy_protocol (HTTP/CONNECT) and the new postgres.proxy_protocol (Postgres). Docs, guides, and example configs are updated to match.
|
Claude finished @andybons's task in 2m 49s —— View job Review: PROXY protocol support + per-listener config (#55)Todo list
Note: this sandbox's SummaryClean, well-tested change. I traced the full data flow for both listeners and didn't find correctness or security bugs.
Postgres wiring order ( Config rename — Security — the doc language correctly frames Minor, non-blocking observation: the malformed-header detection in No security, race, or logic issues found. Test coverage is genuinely test-first (the PR description's failing-red-first claims check out against the actual test names/assertions, e.g. |
What
Adds PROXY protocol support to the Postgres data-plane listener, and reorganizes the PROXY protocol config to be per-listener — mirroring how HAProxy attaches
accept-proxyto eachbind.Config (breaking, pre-1.0)
proxy.proxy_protocol— the HTTP/CONNECT listener (renamed fromnetwork.proxy_protocol)postgres.proxy_protocol— the Postgres data-plane listener (new)network.proxy_protocol— removed, no alias. PROXY protocol is a listener-ingress concern, not network allow/deny policy, so it never belonged undernetwork:. Migration: move the line underproxy:.Behavior
Both listeners use identical semantics via a shared
proxy.WrapProxyProtocolListenerhelper (deduplicated from the old inline HTTP path): fail-openUSEpolicy (header honored if present; fall back to the raw TCP peer address if absent or on a 10s header-read timeout), and a DEBUG log on malformed headers. On the Postgres plane the advertised address flows intoClientAddrand the run-token-auth log automatically. The PROXY header is consumed before the Postgres SSLRequest and TLS handshake, so interception is unaffected. (go-proxyproto parses both v1 and v2.)Security:
client_ip/client_addris logging-only on both listeners (auth is the constant-time run token; nothing keys off the advertised address), so a forged header only pollutes logs — enable only when the port is reachable solely through the LB. Documented in the config reference, guide 11, and the observability concept page.Fixes folded in (surfaced by review)
WrapProxyProtocolListener'sAcceptwas callingconn.RemoteAddr()unconditionally, triggering the lazy blocking header read on the accept loop — a slow-loris stall of all new connections. Now uses the side-effect-freeRaw().RemoteAddr()for proxyproto conns. This was pre-existing in the v0.17.0 HTTP path, so this hardens the HTTP listener too.enableKeepAlive's*net.TCPConnassertion silently failed through the PROXY wrapper (losing keepalives); fixed via a documentedunderlyingTCPConnunwrap.Tests
Test-first throughout: Postgres v1 header →
ClientAddr= advertised IP (red before wiring), fail-open no-header fallback, disabled-default, HTTP via the new field, the Accept-doesn't-block-on-silent-client regression test, and the keepalive-unwrap test (mutation-checked). Live-verified end-to-end: both listeners, real CA, real handshakes.go test -race ./..., vet, gofmt clean.Docs
CHANGELOG
v0.18.0(Added: Postgres support; Changed: the breaking rename + per-listener model). Config reference, guide 11 (now covers the Postgres port), observability page, README, and both example configs updated.